package com.cpxj.aspect;

import com.cpxj.annotation.GuavaRetry;
import com.cpxj.annotation.RetryStrategyEnum;
import com.cpxj.common.YmmResult;
import com.github.rholder.retry.*;
import com.google.common.base.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;

import javax.annotation.Nullable;
import java.lang.reflect.Method;

@Aspect
@Slf4j
public class GuavaRetryAspect {

    @Around(value = "@annotation(com.cpxj.annotation.GuavaRetry)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        GuavaRetry ann = AnnotationUtils.findAnnotation(method, GuavaRetry.class);
        Assert.notNull(ann, "Cannot found annotation @LogMessage.");

        int attemptNumber = ann.attemptNumber();

        if (ann.attemptNumber() <= 1) {
            return pjp.proceed();
        }
        RetryerBuilder retryerBuilder = RetryerBuilder.newBuilder();
        retryerBuilder.withRetryListener(new MyRetryListener(pjp))
                .withStopStrategy(StopStrategies.stopAfterAttempt(attemptNumber))
                .withBlockStrategy(BlockStrategies.threadSleepStrategy());

        if (ann.exceptionClass().length > 0) {
            for (Class retryThrowable : ann.exceptionClass()) {
                if (retryThrowable != null && Throwable.class.isAssignableFrom(retryThrowable)) {
                    retryerBuilder.retryIfExceptionOfType(retryThrowable);
                }
            }
        } else {
            retryerBuilder.retryIfException();
        }
        RetryStrategyEnum retryStrategyEnum = ann.RetryStrategy();
        if (RetryStrategyEnum.FIXTIME.equals(retryStrategyEnum)) {
            retryerBuilder.withWaitStrategy(WaitStrategies.fixedWait(ann.retryTime(), ann.timeUnit()));
        } else {
            retryerBuilder.withWaitStrategy(WaitStrategies.incrementingWait(ann.initialSleepTime(), ann.timeUnit(), ann.incrementSleepTime(), ann.timeUnit()));
        }

        Class<?> returnType = method.getReturnType();
        if(returnType == YmmResult.class){
            retryerBuilder.retryIfResult(new Predicate() {
                @Override
                public boolean apply(@Nullable Object input) {
                    if(input instanceof YmmResult){
                        YmmResult ymmResult = (YmmResult) input;
                        if(!ymmResult.isSuccess()){
                            return true;
                        }
                    }
                    return false;
                }
            });
        }

        return retryerBuilder.build().call(() -> {
            try {
                return pjp.proceed();
            } catch (Throwable throwable) {
                throw new Exception(throwable);
            }
        });
    }


    static class MyRetryListener implements RetryListener {

        private ProceedingJoinPoint pjp;

        MyRetryListener(ProceedingJoinPoint pjp) {
            this.pjp = pjp;
        }

        @Override
        public <V> void onRetry(Attempt<V> attempt) {
            String className = pjp.getTarget().getClass().getName();
            String methodName = pjp.getSignature().getName();
            log.info(className + "#" + methodName + "重试第{}次", attempt.getAttemptNumber());
        }
    }

    public static Method getMethod(JoinPoint point) {
        return getMethodSignature(point).getMethod();
    }

    public static MethodSignature getMethodSignature(JoinPoint point) {
        return (MethodSignature) point.getSignature();
    }
}
